package cmm.Parser;

import cmm.Lexer.TokenType;
import jdk.nashorn.internal.runtime.ParserException;
import cmm.Lexer.Token;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import cmm.Exception.ParseException;
import sun.security.krb5.internal.PAEncTSEnc;

/**
 * Created by zhangzheyu on 2017/10/27.
 */
public class Parser {
    private LinkedList<TreeNode> treeNodes;
    private Token currentToken = null;
    private ListIterator<Token> iterator = null;
    private boolean isInWhile = false;
    public LinkedList<String> fuctions = new LinkedList<String>();
    private StringBuffer buffer =new StringBuffer();
    public LinkedList<TreeNode> parse(LinkedList<Token> tokens) throws ParseException {
        treeNodes = new LinkedList<TreeNode>();
        iterator =tokens.listIterator();
        while(iterator.hasNext()){
            treeNodes.add(parseStmt());
        }
        return  treeNodes;
    }
    public TreeNode parseStmt() throws ParseException {
        if(getNextTokenType() == TokenType.IF){
            return parseIf();
        }else if (getNextTokenType() == TokenType.WHILE){
            return parseWhile();
        }else if (getNextTokenType() == TokenType.READ) {
            return parseRead();
        }else if (getNextTokenType() == TokenType.WRITE){
            return parseWrite();
        }else if (getNextTokenType() == TokenType.INT) {
            return parseDeclare();
        }else if (getNextTokenType() == TokenType.REAL) {
            return parseDeclare();
        }else if (getNextTokenType() == TokenType.LBRACE) {
            return parseBlock();
        }else if (getNextTokenType() == TokenType.FOR) {
            return parseFor();
        }else if (getNextTokenType() == TokenType.ID ) {
            currentToken = iterator.next();
            if (getNextTokenType() == TokenType.LPARENT){
                return parseFunctionCall();
            }else {
                currentToken = iterator.previous();
                return parseAssign();
            }
        }else if (getNextTokenType() == TokenType.BREAK) {
            return parseBreak();
        }
        else if (getNextTokenType() == TokenType.RETURN) {
            return parseReturn();
        }else{
            throw new ParserException("line" + getNextTokenLineNo() + ": expected token");
        }

    }
    public TreeNode parseIf() throws ParseException {
        TreeNode node = new TreeNode(NodeType.IF_STMT);
        consumeNextToken(TokenType.IF);
        consumeNextToken(TokenType.LPARENT);
        node.addtoChildnode(parseExp(),node);
        consumeNextToken(TokenType.RPARENT);
        node.addtoChildnode(parseStmt(),node);
        if (getNextTokenType() == TokenType.ELSE){
            consumeNextToken(TokenType.ELSE);
            node.addtoChildnode(parseStmt(),node);
        }
        return node;

    }
    public TreeNode parseWhile() throws ParseException {
        TreeNode node  = new TreeNode(NodeType.WHILE_STMT);
        consumeNextToken(TokenType.WHILE);
        consumeNextToken(TokenType.LPARENT);
        isInWhile = true;
        node.addtoChildnode(parseExp(),node);
        consumeNextToken(TokenType.RPARENT);
        node.addtoChildnode(parseStmt(),node);
        isInWhile = false;
        return node;
    }
    public TreeNode parseBreak() throws ParseException {
        if (iterator.hasNext()){
            TreeNode node = new TreeNode(NodeType.BREAK);
            currentToken = iterator.next();
            if(isInWhile == true){
                node.setValue(currentToken.getValue());
                node.setDataType(currentToken.getType());
                consumeNextToken(TokenType.SEMI);
                return node;
            }
        }
        throw new ParserException("line " + currentToken.getLineNum() + ": token break can not be here" );

    }
    public TreeNode parseRead() throws ParseException {
        TreeNode node = new TreeNode(NodeType.READ_STMT);
        consumeNextToken(TokenType.READ);
        node.addtoChildnode(variableName(),node);
        consumeNextToken(TokenType.SEMI);
        return node;

    }
    public TreeNode parseWrite() throws ParseException {
        TreeNode node = new TreeNode(NodeType.WRITE_STMT);
        node.addtoChildnode((new TreeNode(NodeType.SYMBOL,TokenType.WRITE,"write")),node);
        consumeNextToken(TokenType.WRITE);
        consumeNextToken(TokenType.LPARENT);
        node.addtoChildnode(parseExp(),node);
        consumeNextToken(TokenType.RPARENT);

        consumeNextToken(TokenType.SEMI);
        return node;

    }
    public TreeNode parseDeclare() throws ParseException {
        TreeNode node = new TreeNode(NodeType.DECLARE_STMT);
        TreeNode varNode = new TreeNode(NodeType.VAR);
        if (checkNextTokenType(TokenType.INT, TokenType.REAL)) {
            currentToken = iterator.next();
            TokenType type = currentToken.getType();
            if (type == TokenType.INT) {
                varNode.setDataType(TokenType.INT);
                varNode.setValue("int");
            } else {
                varNode.setDataType(TokenType.REAL);
                varNode.setValue("real");
            }
        } else {
            throw new ParserException("line " + " " + currentToken.getLineNum() + " " + currentToken.getValue() + ": next token should be variable type");
        }
        node.addtoChildnode(varNode,node);

        if (getNextTokenType() == TokenType.LBRACKET) {
//            TreeNode node1 = new TreeNode(NodeType.VAR);
//            node.addtoChildnode(node1,node);
            consumeNextToken(TokenType.LBRACKET);
//            node1.addtoChildnode(parseExp(),node1);
            varNode.addtoChildnode(parseExp(),varNode);
            consumeNextToken(TokenType.RBRACKET);
        }
        while (true) {
            TreeNode childNode = new TreeNode(NodeType.VAR);
            childNode.setDataType(varNode.getDataType());
            if (checkNextTokenType(TokenType.ID)) {
                currentToken = iterator.next();
                childNode.setValue(currentToken.getValue());
                if (getNextTokenType() == TokenType.LPARENT) {
                    node.addtoChildnode(parseFunctionDeclare(),node);
                    return node;
                }
            } else {
                throw new ParserException("line" + getNextTokenLineNo() + ": next token should be ID");
            }
            if (getNextTokenType() == TokenType.ASSIGN) {
                childNode.addtoChildnode((new TreeNode(NodeType.SYMBOL, TokenType.ASSIGN, "=")),childNode);
                consumeNextToken(TokenType.ASSIGN);
                childNode.addtoChildnode(parseExp(),childNode);
            } else if (getNextTokenType() == TokenType.LBRACKET) {
                throw new ParseException("line " + getNextTokenLineNo() + " " + "position "+ currentToken.getPosition() + "syntax error : the declare of array cannot be like this.");
//                consumeNextToken(TokenType.LBRACKET);
//                childNode.addtoChildnode(parseExp(),childNode);
//                consumeNextToken(TokenType.RBRACKET);
            }
            node.addtoChildnode(childNode,node);
            if (checkNextTokenType(TokenType.COMMA)) {
                consumeNextToken(TokenType.COMMA);
                continue;
            } else {
                break;
            }
        }
        consumeNextToken(TokenType.SEMI);

        return node;
    }
    public TreeNode parseAssign() throws ParseException {
        TreeNode node = new TreeNode(NodeType.ASSIGN_STMT);
        node.addtoChildnode(variableName(),node);
        node.addtoChildnode((new TreeNode(NodeType.SYMBOL,TokenType.ASSIGN,"=")),node);
        consumeNextToken(TokenType.ASSIGN);
        if (getNextTokenType() == TokenType.ID){
            currentToken = iterator.next();
            if (getNextTokenType() == TokenType.LPARENT){
                node.addtoChildnode(parseFunctionCall(),node);
                consumeNextToken(TokenType.SEMI);
                return node;
            }
            currentToken = iterator.previous();
        }
        node.addtoChildnode(parseExp(),node);

        consumeNextToken(TokenType.SEMI);
        return node;

    }
    public TreeNode parseFor()throws ParseException{
        TreeNode node = new TreeNode(NodeType.FOR_STMT);
        consumeNextToken(TokenType.FOR);
        consumeNextToken(TokenType.LPARENT);
        if (getNextTokenType() == TokenType.ID){
            node.addtoChildnode(parseAssign(),node);
        }else if (getNextTokenType() ==  TokenType.INT || getNextTokenType() == TokenType.REAL){
            node.addtoChildnode(parseDeclare(),node);
        }
        //consumeNextToken(TokenType.SEMI);
        node.addtoChildnode(parseExp(),node);
        consumeNextToken(TokenType.SEMI);
        //node.addtoChildnode(parseAssign());
        consumeNextToken(TokenType.RPARENT);
        node.addtoChildnode(parseStmt(),node);
        return node;

    }
    public TreeNode parseFunctionCall()throws ParseException{
        TreeNode node = new TreeNode(NodeType.FUNCTION_CALL);
        //currentToken = iterator.next();
        TreeNode signature = new TreeNode(NodeType.FUNCTION_SIGN);
        signature.setDataType(TokenType.ID);
        signature.setValue(currentToken.getValue());
        consumeNextToken(TokenType.LPARENT);
        while (getNextTokenType() != TokenType.RPARENT){
            if(!checkNextTokenType(TokenType.COMMA)){
                node.addtoChildnode(parseExp(),node);
            }else if (getNextTokenType() == TokenType.COMMA){
                consumeNextToken(TokenType.COMMA);
                continue;
            }
            continue;
        }
        consumeNextToken(TokenType.RPARENT);
        return node;
    }
    public TreeNode parseFunctionDeclare() throws ParseException{
        TreeNode node  = new TreeNode(NodeType.FUNCTION_DECL);
        currentToken = iterator.next();
        TreeNode signature = new TreeNode(NodeType.FUNCTION_SIGN);
        signature.setDataType(TokenType.ID);
        signature.setValue(currentToken.getValue());
        fuctions.add(currentToken.getValue());
        //consumeNextToken(TokenType.LPARENT);
        node.addtoChildnode(parseParaList(),node);
        consumeNextToken(TokenType.RPARENT);
        node.addtoChildnode(parseStmt(),node);
        return node;
    }
    public TreeNode parseParaList() throws ParseException{
        TreeNode node  =new TreeNode(NodeType.PARALIST);
        //consumeNextToken(TokenType.LPARENT);
        currentToken =iterator.next();
        while (!checkNextTokenType(TokenType.RPARENT)){
            if (currentToken.getType() == TokenType.INT || currentToken.getType() == TokenType.REAL){
                TokenType type= currentToken.getType();
                if (getNextTokenType() == TokenType.ID){
                    currentToken = iterator.next();
                    node.addtoChildnode((new TreeNode(NodeType.VAR,currentToken.getType(),currentToken.getValue())),node);
                }else {
                    throw new ParseException("Line" + currentToken.getLineNum() + "position" + currentToken.getPosition() + ":next token be an ID");
                }
            }
            if (getNextTokenType() == TokenType.COMMA){
                consumeNextToken(TokenType.COMMA);
                currentToken =iterator.next();
                continue;
            }
        }
        return node;
    }
    public TreeNode parseReturn()throws ParseException{
        TreeNode node = new TreeNode(NodeType.RETURN_STMT);
        consumeNextToken(TokenType.RETURN);
        node.addtoChildnode(parseExp(),node);
        consumeNextToken(TokenType.SEMI);
        return node;
    }
    public TreeNode parseBlock() throws ParseException {
        TreeNode node  = new TreeNode(NodeType.NULL);
        TreeNode header = node;
        TreeNode temp = null;
        consumeNextToken(TokenType.LBRACE);
        while (getNextTokenType() != TokenType.RBRACE){
            temp= parseStmt();
            node.addtoChildnode(temp,node);
            //node =temp;
        }
        consumeNextToken(TokenType.RBRACE);
        return  header;

    }
    public TreeNode parseExp() throws ParseException {
        TreeNode node  = new TreeNode(NodeType.EXP);
        node.setDataType(TokenType.LOGIC_EXP);
        TreeNode leftNode = additiveExp();
        if(checkNextTokenType(TokenType.EQ,TokenType.NEQ,TokenType.GT,TokenType.GET,TokenType.LT,TokenType.LET,TokenType.TRUE,TokenType.FALSE)){
            node.addtoChildnode(leftNode,node);
            node.addtoChildnode(logicalOperator(),node);
            node.addtoChildnode(additiveExp(),node);
        }else{
            return leftNode;
        }
        return node;


    }
    public TreeNode additiveExp() throws ParseException {
        TreeNode node = new TreeNode(NodeType.EXP);
        node.setDataType(TokenType.ADDTIVE_EXP);
        TreeNode leftNode  = term();
        if(checkNextTokenType(TokenType.PLUS)){
            node.addtoChildnode(leftNode,node);
            node.addtoChildnode(addMinusOperator(),node);
            node.addtoChildnode(additiveExp(),node);
        }else if (checkNextTokenType(TokenType.MINUS)){
            node.addtoChildnode(leftNode,node);
            node.addtoChildnode(addMinusOperator(),node);
//            TreeNode opnode = new TreeNode(NodeType.OP);
//            node.addtoChildnode(opnode,node);
            node.addtoChildnode(additiveExp(),node);
        }else {
            return  leftNode;
        }
        return node;

    }
    public TreeNode term() throws ParseException {
        TreeNode node = new TreeNode(NodeType.EXP);
        node.setDataType(TokenType.TERM_EXP);
        TreeNode leftNode = factor();
        if(checkNextTokenType(TokenType.MUL,TokenType.DIV)){
            node.addtoChildnode(leftNode,node);
            node.addtoChildnode(MulDivOperator(),node);
            node.addtoChildnode(term(),node);
        }else{
            return leftNode;
        }
        return node;

    }
    public TreeNode factor() throws ParseException {
        if (iterator.hasNext()){
            TreeNode expNode = new TreeNode(NodeType.FACTOR);
            if(getNextTokenType() == TokenType.LITERAL_INT || getNextTokenType() == TokenType.LITERAL_REAL){
                expNode.addtoChildnode(literal(),expNode);
            }else if (getNextTokenType() == TokenType.LPARENT){
                consumeNextToken(TokenType.LPARENT);
                expNode = parseExp();
                consumeNextToken(TokenType.RPARENT);
            }else if (getNextTokenType() == TokenType.PLUS){
                expNode.setDataType(TokenType.PLUS);
                currentToken = iterator.next();
                expNode.addtoChildnode(term(),expNode);
            }else if (getNextTokenType() == TokenType.MINUS){
                expNode.setDataType(TokenType.MINUS);
                currentToken = iterator.next();
                expNode.addtoChildnode(term(),expNode);
            }else{
                return variableName();
            }
            return expNode;
        }
        throw new ParserException("line" + getNextTokenLineNo() + " : next token should be factor");

    }
    public TreeNode literal() throws ParseException {
        if(iterator.hasNext()){
            currentToken =iterator.next();
            TokenType  type = currentToken.getType();
            TreeNode node = new TreeNode(NodeType.LITREAL);
            node.setDataType(type);
            node.setValue(currentToken.getValue());
            if (type == TokenType.LITERAL_INT|| type == TokenType.LITERAL_REAL){
                return node;
            }
        }
        throw new ParserException("line " + getNextTokenLineNo() + ": next token should be literal");

    }
    public TreeNode logicalOperator() throws ParseException {
        if (iterator.hasNext()){
            currentToken = iterator.next();
            TokenType type = currentToken.getType();
            if(type == TokenType.EQ
                    ||type == TokenType.NEQ
                    ||type == TokenType.LT
                    ||type == TokenType.LET
                    ||type == TokenType.GT
                    ||type == TokenType.GET
                    ){
                TreeNode node = new TreeNode(NodeType.OP,currentToken.getType(),currentToken.getValue());
                node.setDataType(type);
                node.setValue(currentToken.getValue());
                return node;
            }
        }
        throw new ParserException("line " + getNextTokenLineNo() + ": next token should be logical operator");

    }
    public TreeNode addMinusOperator() throws ParseException {
        if(iterator.hasNext()){
            currentToken = iterator.next();
            TokenType type = currentToken.getType();
            if (type == TokenType.PLUS || type == TokenType.MINUS){
                TreeNode node  =new TreeNode(NodeType.OP);
                node.setDataType(type);
                node.setValue(currentToken.getValue());
                return node;
            }
        }
        throw new ParserException("line " + getNextTokenLineNo() +"position " +currentToken.getPosition()+ ": next token should be additive or minus operator");

    }
    public TreeNode MulDivOperator() throws ParseException {
        if(iterator.hasNext()){
            currentToken = iterator.next();
            TokenType type = currentToken.getType();
            if (type == TokenType.MUL || type == TokenType.DIV){
                TreeNode node  =new TreeNode(NodeType.OP);
                node.setDataType(type);
                node.setValue(currentToken.getValue());
                return node;
            }
        }
        throw new ParserException("line " + getNextTokenLineNo() + "position " +currentToken.getPosition()+" :next token should be multiple or divide operator");
    }
    public TreeNode variableName() throws ParseException {
        TreeNode node = new TreeNode(NodeType.VAR);
        if (iterator.hasNext()){
            currentToken = iterator.next();
            node.setDataType(currentToken.getType());
            node.setValue(currentToken.getValue());
        }else{
            throw new ParserException("line " + getNextTokenLineNo() + ": next token should be ID");
        }
        if (getNextTokenType() == TokenType.LBRACKET){
            consumeNextToken(TokenType.LBRACKET);
            node.addtoChildnode(parseExp(),node);
            consumeNextToken(TokenType.RBRACKET);
        }
        return node;

    }
    public void consumeNextToken(TokenType type) throws ParseException {
        if (iterator.hasNext()){
            currentToken = iterator.next();
            if (currentToken.getType() == type){
                return;
            }
        }
        throw new ParseException("line" + " "+currentToken.getLineNum()+" " +currentToken.getValue()+": next token should be ->" + type.toString());

    }
    public boolean checkNextTokenType(TokenType ... type) throws ParseException {
        if(iterator.hasNext()){
            TokenType nextType = iterator.next().getType();
            iterator.previous();
            for(TokenType tokenType : type){
                if(nextType == tokenType)
                    return true;
            }
        }
        return false;

    }
    public TokenType getNextTokenType(){
        if (iterator.hasNext()){
            TokenType type = iterator.next().getType();
            iterator.previous();
            return type;
        }
        return null;
    }
    public int getNextTokenLineNo(){
        if (iterator.hasNext()){
            int lineNum = iterator.next().getLineNum();
            iterator.previous();
            return lineNum;
        }
        return -1;
    }
    public TokenType getPreviousTokenType(){
        if (iterator.hasPrevious()){
            TokenType type = iterator.previous().getType();
            iterator.next();
            return type;
        }
        return null;
    }
    public void printtoTree(){
        Iterator<TreeNode> treeNodeIterator = treeNodes.iterator();
        while (treeNodeIterator.hasNext()){
            printAST(treeNodeIterator.next(),0);
        }
    }
    public void printAST(TreeNode node, int n){
        Iterator<TreeNode> child = node.getChildnodes().iterator();
        for(int i =0;i<n ;i++){
            System.out.print(" ");
        }

        if (node.getValue() != null){
            System.out.print(node.getType()+ " " );
            if (node.getFather()!=null){
                System .out.print(node.getFather().getType().toString()+ " ");
            }
            System.out.print(node.getValue() + "\n");
        }else{
            System.out.print(node.getType()+ " "+ "\n" );
        }
        while(child.hasNext()){
            printAST(child.next(),n+4);
        }
    }
    public void bufferAST(TreeNode node, int n){
        Iterator<TreeNode> child = node.getChildnodes().iterator();
        for(int i =0;i<n ;i++){
            buffer.append(" ");
        }

        if (node.getValue() != null){
            buffer.append(node.getType()+ " " );
            if (node.getFather()!=null){
                buffer.append(node.getFather().getType().toString()+ " ");
            }
            buffer.append(node.getValue() + "\n");
        }else{
            buffer.append(node.getType()+ " "+ "\n" );
        }
        while(child.hasNext()){
            bufferAST(child.next(),n+4);
        }
    }
    public StringBuffer formStringBuffer(){
        Iterator<TreeNode> treeNodeIterator = treeNodes.iterator();
        while (treeNodeIterator.hasNext()){
            bufferAST(treeNodeIterator.next(),0);
        }
        return buffer;
    }

    public LinkedList<TreeNode> getTreeNodes() {
        return treeNodes;
    }

}
